import { RoomModel } from "../game/model/RoomModel";
import { UserModel } from "../game/model/UserModel";
import { UIHelper } from "../game/util/UIHelper";
import { JhaoProto } from "../lib/net/protobuf/jhaoproto";
import { ProtoManager, WsSendData } from "../lib/net/protobuf/ProtoManager";
import { ServerConfig } from "../net/ServerConfig";
import { SocketClient } from "../net/SocketClient";
import DateUtil from "./DateUtil";
import { EngineConfig } from "./EngineConfig";
import { EventManager } from "./EventManager";
import { GameWebSocket, GameWebSocketStatus } from "./GameWebSocket";
import { Logger } from "./Logger";
import SocketRequestData from "./SocketRequestData";
import UIManager from "./UIManager";

/**
 * 游戏socket通信，断线重连，处理消息等
 */
export class GameSocket {

    private socket: GameWebSocket;
    public isConnected: boolean = false;
    private isConnecting: boolean = false;
    private requestMsgQue: Array<SocketRequestData> = [];
    private requestMsgMap: Map<number, SocketRequestData> = new Map<number, SocketRequestData>(); //请求信息数据
    private static _instance: GameSocket;

    private timeOutId;
    private pingTimeId;
    private pingInteval: number = 3 * 1000;
    private sendTimeCount: number = 13 * 1000;//秒断开socket
    private connectTimeMax: number = 3 * 1000;
    private lastRecvTime: number = 0;

    public isNeedReconnect: boolean = false;
    private reconnectCount: number = 0;
    public reconnectTimes: number = 0;
    private maxReconnectTimes: number = 3;
    private connectTimeId;

    private onlyId: number = 1;

    private callbackMap: Map<number, Function> = new Map<number, Function>();

    private lastHideTime: number = 0;
    public isKick: boolean = false;
    private checkReConnectCount: number = 0;

    private offlineTimes: number = 0

    private checkOnlyId() {
        let abc: number = this.onlyId;
        this.onlyId++;
        if (this.onlyId >= Number.MAX_SAFE_INTEGER) {
            this.onlyId = 1;
        }
        return abc;
    }

    public static getInstance(): GameSocket {
        if (!GameSocket._instance) {
            GameSocket._instance = new GameSocket();
        }
        return GameSocket._instance;
    }

    constructor() {
        this.init();
    }

    public init() {
        this.socket = new GameWebSocket();
        EventManager.instance.clear(this);
        EventManager.instance.on(EngineConfig.Event_Type.Event_FrameUpdate, this.onFrameUpdate, this)
        this.isConnected = false;
        this.isConnecting = false;
        cc.game.off(cc.game.EVENT_HIDE, this.onHideToBackground, this);
        cc.game.on(cc.game.EVENT_HIDE, this.onHideToBackground, this);
        cc.game.off(cc.game.EVENT_SHOW, this.aWakeFromBackGround, this);
        cc.game.on(cc.game.EVENT_SHOW, this.aWakeFromBackGround, this);
        this.socket.SetCallBacks(this.onConnect, this.onClose, this.onSocketData, this.onError, this);
    }

    /**
     * 隐藏程序
     */
    private onHideToBackground() {
        this.lastHideTime = DateUtil.now();
        Logger.log("onHideToBackground");
    }

    /**
     *  从后台唤醒
     */
    private aWakeFromBackGround() {
        Logger.log("aWakeFromBackGround");
        if (UserModel.instance.isLogin) {
            if (RoomModel.instance.isPlayingGame) {
                if ((DateUtil.now() - this.lastHideTime) >= 3 * 1000) {
                    this.closeSocket()
                }
            } else {
                if ((DateUtil.now() - this.lastHideTime) >= 5 * 1000) {
                    this.closeSocket()
                }
            }
        }
    }

    /**
     * 链接成功回调
     * @param event 
     */
    private onConnect() {
        if (this.timeOutId != undefined) {
            clearTimeout(this.timeOutId);
        }
        this.checkReConnectCount = 0
        this.lastHideTime = DateUtil.now();
        this.isConnected = true;
        this.isConnecting = false;
        this.reconnectTimes = 0;
        clearTimeout(this.connectTimeId);
        this.ping()
        if (this.isNeedReconnect) {
            // SocketClient.reLogin();
            Logger.log("gamesocket==onConnect=isNeedReconnect==", this.isNeedReconnect)
            this.requestMsgQue = []
            if (UserModel.instance.room_id > 0) {
                SocketClient.login(true)
            } else {
                SocketClient.login(false)
            }
        } else {
            Logger.log("onConnect=====", this.requestMsgQue)
            this.checkSendPack();
        }
    }

    /**
     * 断开链接
     * @param e 
     */
    private onClose() {
        // Logger.log("onClose===", this.isConnected, this.isReconnecting)
        this.isConnected = false;
        this.isConnecting = false;
        clearTimeout(this.connectTimeId);
        UserModel.instance.isLogin = false
        UserModel.instance.isLogining = false
        Logger.log("GameSocket onClose", this.isNeedReconnect);

        if (this.isNeedReconnect && !this.isKick) {
            ServerConfig.setHallServerSocket()
            this.reConnect();
        }
    }

    /**
     * 链接错误
     * @param e 
     */
    private onError(e: Event) {
        console.log("GameSocket onError");
        this.isConnecting = false;
    }

    /**
     * 链接 服务器
     * @param nowSoskcetUrl 
     */
    private connect(nowSoskcetUrl: string = "") {
        // Logger.log("connnect====", nowSoskcetUrl)
        nowSoskcetUrl = ServerConfig.getSocketUrl()
        // Logger.log("connnect===11=", nowSoskcetUrl, this.isConnected, this.isConnecting)
        if (this.isConnected || this.isConnecting) return;
        Logger.log("connnect==22==", nowSoskcetUrl, this.isConnected, this.isConnecting)
        this.isConnecting = true;
        // this.connectTimeId = setTimeout(() => {

        // }, this.connectTimeMax);

        if (this.socket) {
            Logger.log("存在this.socket")
            this.socket.connect(nowSoskcetUrl);
        } else {
            Logger.log("不存在this.socket")
            this.init()
            this.socket.connect(nowSoskcetUrl);
        }
    }

    private reConnect() {
        this.reconnectTimes++;
        if (this.reconnectTimes >= this.maxReconnectTimes) {
            UIHelper.showCheck("网络异常，请稍后再试", () => {
                this.reconnectTimes = 0
                this.reconnectCount = 1
                this.connect()
            })
        } else {
            this.reconnectCount = 1 * cc.game.getFrameRate();
        }
    }

    private checkReConnect() {
        if (this.isNeedReconnect) {
            this.reconnectCount--;
            if (this.reconnectCount == 0) {
                this.connect();
            }
        }
    }

    private onFrameUpdate() {
        this.checkReConnect();
    }

    /**心跳 */
    private pingRequest() {
        if (this.isConnected) {
            SocketClient.ping()
            clearTimeout(this.timeOutId)
            // Logger.log("pingRequest====", this.pingInteval)
            this.timeOutId = setTimeout(() => {
                // Logger.log("pingRequest==超时==", this.isConnected)
                if (this.isConnected) {
                    GameSocket.getInstance().closeSocket(true)
                }
            }, this.pingInteval + 1 * 1000)
        }
    }

    /** 发送心跳 */
    public ping() {
        if (!this.isConnected) return;
        this.pingRequest();
        clearTimeout(this.pingTimeId);
        this.pingTimeId = setTimeout(() => {
            this.ping();
        }, this.pingInteval);
    }

    /**
     * 检测发送包
     */
    private checkSendPack() {
        if (this.requestMsgQue.length > 0) {
            let socketData: SocketRequestData = this.requestMsgQue.shift();
            if (this.isConnected) {
                let data = this.createSendPack(socketData)
                this.socket.send(data);
                this.checkSendPack();
            } else {
                this.reConnect();
            }
        }
    }

    /**
     * 创建发送包
     * @param protoPack 
     */
    private createSendPack(socketData: SocketRequestData) {
        let send_data: JhaoProto.IBaseMsg = {
            cmdOrder: socketData.id,
            cmdIndex: socketData.data.cmd_index,
            timeUpload: DateUtil.now(),
            data: ProtoManager.instance.encode(socketData.data.en_proto_ctor, socketData.data.data)
        }
        // Logger.log("createSendPack==", send_data)
        this.requestMsgMap.set(socketData.id, socketData)
        return ProtoManager.instance.encode(JhaoProto.BaseMsg, send_data)
    }

    /**
     * 外部接口调用发命令到服务器
     * @param sendData 
     */
    public sendToSocket(sendData: WsSendData) {
        let reqData: SocketRequestData = new SocketRequestData();
        reqData.id = this.checkOnlyId();
        reqData.data = sendData;
        this.requestMsgQue.push(reqData);
        if(sendData.cmd_index != JhaoProto.CmdIndex.Ping){
            // Logger.log("sendToSocket===", reqData)
        }
        if (this.isConnected) {
            this.checkSendPack();
        }
        else {
            // Logger.log("sendToSocket==222=", reqData, this.isConnected, this.isConnecting)
            if (!this.isConnecting) {
                // Logger.log("sendToSocket==333=", reqData, this.isConnected, this.isConnecting)
                this.connect();
            }
        }
    }

    /**
     * 消息接收处理
     * @param socketData 
     */
    private onSocketData(socketData: ArrayBuffer) {
        clearTimeout(this.timeOutId)
        this.lastRecvTime = DateUtil.now();
        try {
            let byteArray: Uint8Array = new Uint8Array(socketData);
            let basemsg: JhaoProto.BaseMsg = ProtoManager.instance.decode(JhaoProto.BaseMsg, byteArray)
            
            // let totalPack = ProtoUtils.DeserializeBinary(ProtoConfig.basemsg, byteArray);
            // let id: number = totalPack.getId()
            // let protoName: string = totalPack.getProtoName();
            // let protoData = totalPack.getData();
            let socketReqData: SocketRequestData | undefined = this.requestMsgMap.get(basemsg.cmdOrder);
            // Logger.log("onSocketData==", basemsg, socketReqData)
            if (socketReqData) {
                this.requestMsgMap.delete(basemsg.cmdOrder)
                if (socketReqData.data.callback) {
                    socketReqData.data.callback(basemsg)
                } else {
                    SocketClient.onSocketRespMsg(basemsg);
                }
            }else{
                SocketClient.onSocketRespMsg(basemsg);
            }
        } catch (error) {
            Logger.log("onSocketData error", error)
        }
    }

    /**
     * 断开链接
     * @param isNeedReconnect 
     */
    public closeSocket(isNeedReconnect: boolean = true) {
        if (isNeedReconnect) {
            UIManager.instance.showLoadingIcon()
        }
        UserModel.instance.isLogin = false
        UserModel.instance.isLogining = false
        this.offlineTimes = 0
        this.isConnecting = false;
        this.isConnected = false;
        this.isNeedReconnect = isNeedReconnect;
        clearTimeout(this.timeOutId)
        clearTimeout(this.pingTimeId);
        Logger.log("closeSocket==", this.isNeedReconnect, this.socket.readyState, GameWebSocketStatus.CLOSED)
        this.socket.close();
        this.socket = null;
        this.init();
    }

}
